// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

// Rule severity guidelines
// ------------------------
//
// Errors are generally printed in red, and may prevent other build tasks from running (e.g. unit tests).
// Developers should never ignore errors.  Warnings are generally printed in yellow, and do not block local
// development, although they must be fixed/suppressed before merging.  Developers will commonly ignore warnings
// until their feature is working.
//
// Rules that should be a WARNING:
// - An issue that is very common in partially implemented work (e.g. missing type declaration)
// - An issue that "keeps things nice" but otherwise doesn't affect the meaning of the code (e.g. naming convention)
// - Security rules -- developers may need to temporarily introduce "insecure" expressions while debugging;
//   if our policy forces them to suppress the lint rule, they may forget to reenable it later.
//
// Rules that should be an ERROR:
// - An issue that is very likely to be a typo (e.g. "x = x;")
// - An issue that catches code that is likely to malfunction (e.g. unterminated promise chain)
// - An obsolete language feature that nobody should be using for any good reason

const { globalIgnores } = require('eslint/config');
const promiseEslintPlugin = require('eslint-plugin-promise');
const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin');
const typescriptEslintParser = require('@typescript-eslint/parser');
const rushstackEslintPlugin = require('@rushstack/eslint-plugin');
const rushstackSecurityEslintPlugin = require('@rushstack/eslint-plugin-security');
const { expandNamingConventionSelectors } = require('./_macros');

const commonNamingConventionSelectors = [
  {
    // We should be stricter about 'enumMember', but it often functions legitimately as an ad hoc namespace.
    selectors: ['variable', 'enumMember', 'function'],

    format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  {
    selectors: ['parameter'],

    format: ['camelCase'],

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Genuine properties
  {
    selectors: ['parameterProperty', 'accessor'],
    enforceLeadingUnderscoreWhenPrivate: true,

    format: ['camelCase', 'UPPER_CASE'],

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // Ignore quoted identifiers such as { "X+Y": 123 }.  Currently @typescript-eslint/naming-convention
        // cannot detect whether an identifier is quoted or not, so we simply assume that it is quoted
        // if-and-only-if it contains characters that require quoting.
        '[^a-zA-Z0-9_]',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Properties that incorrectly match other contexts
  // See issue https://github.com/typescript-eslint/typescript-eslint/issues/2244
  {
    selectors: ['property'],
    enforceLeadingUnderscoreWhenPrivate: true,

    // The @typescript-eslint/naming-convention "property" selector matches cases like this:
    //
    //   someLegacyApiWeCannotChange.invokeMethod({ SomeProperty: 123 });
    //
    // and this:
    //
    //   const { CONSTANT1, CONSTANT2 } = someNamespace.constants;
    //
    // Thus for now "property" is more like a variable than a class member.
    format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // Ignore quoted identifiers such as { "X+Y": 123 }.  Currently @typescript-eslint/naming-convention
        // cannot detect whether an identifier is quoted or not, so we simply assume that it is quoted
        // if-and-only-if it contains characters that require quoting.
        '[^a-zA-Z0-9_]',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  {
    selectors: ['method'],
    enforceLeadingUnderscoreWhenPrivate: true,

    // A PascalCase method can arise somewhat legitimately in this way:
    //
    // class MyClass {
    //    public static MyReactButton(props: IButtonProps): JSX.Element {
    //      . . .
    //    }
    // }
    format: ['camelCase', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Types should use PascalCase
  {
    // Group selector for: class, interface, typeAlias, enum, typeParameter
    selectors: ['class', 'typeAlias', 'enum', 'typeParameter'],
    format: ['PascalCase'],
    leadingUnderscore: 'allow'
  },

  {
    selectors: ['interface'],

    // It is very common for a class to implement an interface of the same name.
    // For example, the Widget class may implement the IWidget interface.  The "I" prefix
    // avoids the need to invent a separate name such as "AbstractWidget" or "WidgetInterface".
    // In TypeScript it is also common to declare interfaces that are implemented by primitive
    // objects, here the "I" prefix also helps by avoiding spurious conflicts with classes
    // by the same name.
    format: ['PascalCase'],

    custom: {
      regex: '^_?I[A-Z]',
      match: true
    }
  }
];

const commonConfig = [
  // Manually authored .d.ts files are generally used to describe external APIs that are  not expected
  // to follow our coding conventions.  Linting those files tends to produce a lot of spurious suppressions,
  // so we simply ignore them.
  globalIgnores(['**/*.d.ts']),

  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: typescriptEslintParser,
      parserOptions: {
        // The "project" path is resolved relative to parserOptions.tsconfigRootDir.
        // Your local .eslintrc.js must specify that parserOptions.tsconfigRootDir=__dirname.
        project: './tsconfig.json',

        // Allow parsing of newer ECMAScript constructs used in TypeScript source code.  Although tsconfig.json
        // may allow only a small subset of ES2018 features, this liberal setting ensures that ESLint will correctly
        // parse whatever is encountered.
        ecmaVersion: 2018,

        sourceType: 'module'
      }
    },
    plugins: {
      '@rushstack': rushstackEslintPlugin,
      '@rushstack/security': rushstackSecurityEslintPlugin,
      '@typescript-eslint': typescriptEslintPlugin,
      promise: promiseEslintPlugin
    },
    rules: {
      // ====================================================================
      // CUSTOM RULES
      // ====================================================================

      // RATIONALE:         See the @rushstack/eslint-plugin documentation
      '@rushstack/no-new-null': 'warn',

      // RATIONALE:         See the @rushstack/eslint-plugin documentation
      '@rushstack/typedef-var': 'warn',

      // RATIONALE:         See the @rushstack/eslint-plugin documentation
      //                    This is enabled and classified as an error because it is required when using Heft.
      //                    It's not required when using ts-jest, but still a good practice.
      '@rushstack/hoist-jest-mock': 'error',

      // ====================================================================
      // SECURITY RULES
      // ====================================================================

      // RATIONALE:         This rule is used to prevent the use of insecure regular expressions, which can lead to
      //                    security vulnerabilities such as ReDoS (Regular Expression Denial of Service).
      '@rushstack/security/no-unsafe-regexp': 'warn',

      // ====================================================================
      // TYPESCRIPT RULES
      // ====================================================================
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/adjacent-overload-signatures': 'warn',

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-unsafe-function-type': 'warn',

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-wrapper-object-types': 'warn',

      // RATIONALE:         We require "x as number" instead of "<number>x" to avoid conflicts with JSX.
      '@typescript-eslint/consistent-type-assertions': 'warn',

      // RATIONALE:         We prefer "interface IBlah { x: number }" over "type Blah = { x: number }"
      //                    because code is more readable when it is built from stereotypical forms
      //                    (interfaces, enums, functions, etc.) instead of freeform type algebra.
      '@typescript-eslint/consistent-type-definitions': 'warn',

      // RATIONALE:         Code is more readable when the type of every variable is immediately obvious.
      //                    Even if the compiler may be able to infer a type, this inference will be unavailable
      //                    to a person who is reviewing a GitHub diff.  This rule makes writing code harder,
      //                    but writing code is a much less important activity than reading it.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/explicit-function-return-type': [
        'warn',
        {
          allowExpressions: true,
          allowTypedFunctionExpressions: true,
          allowHigherOrderFunctions: false
        }
      ],

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/explicit-member-accessibility': 'warn',

      // RATIONALE:         Object-oriented programming organizes code into "classes" that associate
      //                    data structures (the class's fields) and the operations performed on those
      //                    data structures (the class's members).  Studying the fields often reveals the "idea"
      //                    behind a class.  The choice of which class a field belongs to may greatly impact
      //                    the code readability and complexity.  Thus, we group the fields prominently at the top
      //                    of the class declaration.  We do NOT enforce sorting based on public/protected/private
      //                    or static/instance, because these designations tend to change as code evolves, and
      //                    reordering methods produces spurious diffs that make PRs hard to read.  For classes
      //                    with lots of methods, alphabetization is probably a more useful secondary ordering.
      '@typescript-eslint/member-ordering': [
        'warn',
        {
          default: 'never',
          classes: ['field', 'constructor', 'method']
        }
      ],

      // NOTE: This new rule replaces several deprecated rules from @typescript-eslint/eslint-plugin@2.3.3:
      //
      // - @typescript-eslint/camelcase
      // - @typescript-eslint/class-name-casing
      // - @typescript-eslint/interface-name-prefix
      // - @typescript-eslint/member-naming
      //
      // Docs: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md
      '@typescript-eslint/naming-convention': [
        'warn',
        ...expandNamingConventionSelectors(commonNamingConventionSelectors)
      ],

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-array-constructor': 'warn',

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      //
      // RATIONALE:         The "any" keyword disables static type checking, the main benefit of using TypeScript.
      //                    This rule should be suppressed only in very special cases such as JSON.stringify()
      //                    where the type really can be anything.  Even if the type is flexible, another type
      //                    may be more appropriate such as "unknown", "{}", or "Record<k,V>".
      '@typescript-eslint/no-explicit-any': 'warn',

      // RATIONALE:         The #1 rule of promises is that every promise chain must be terminated by a catch()
      //                    handler.  Thus wherever a Promise arises, the code must either append a catch handler,
      //                    or else return the object to a caller (who assumes this responsibility).  Unterminated
      //                    promise chains are a serious issue.  Besides causing errors to be silently ignored,
      //                    they can also cause a NodeJS process to terminate unexpectedly.
      '@typescript-eslint/no-floating-promises': [
        'error',
        {
          checkThenables: true
        }
      ],

      // RATIONALE:         Catches a common coding mistake.
      '@typescript-eslint/no-for-in-array': 'error',

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-misused-new': 'error',

      // RATIONALE:         The "namespace" keyword is not recommended for organizing code because JavaScript lacks
      //                    a "using" statement to traverse namespaces.  Nested namespaces prevent certain bundler
      //                    optimizations.  If you are declaring loose functions/variables, it's better to make them
      //                    static members of a class, since classes support property getters and their private
      //                    members are accessible by unit tests.  Also, the exercise of choosing a meaningful
      //                    class name tends to produce more discoverable APIs: for example, search+replacing
      //                    the function "reverse()" is likely to return many false matches, whereas if we always
      //                    write "Text.reverse()" is more unique.  For large scale organization, it's recommended
      //                    to decompose your code into separate NPM packages, which ensures that component
      //                    dependencies are tracked more conscientiously.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-namespace': [
        'warn',
        {
          // Discourage "namespace" in .ts and .tsx files
          allowDeclarations: false,

          // Allow it in .d.ts files that describe legacy libraries
          allowDefinitionFiles: false
        }
      ],

      // RATIONALE:         Parameter properties provide a shorthand such as "constructor(public title: string)"
      //                    that avoids the effort of declaring "title" as a field.  This TypeScript feature makes
      //                    code easier to write, but arguably sacrifices readability:  In the notes for
      //                    "@typescript-eslint/member-ordering" we pointed out that fields are central to
      //                    a class's design, so we wouldn't want to bury them in a constructor signature
      //                    just to save some typing.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/parameter-properties': 'warn',

      // RATIONALE:         When left in shipping code, unused variables often indicate a mistake.  Dead code
      //                    may impact performance.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-unused-vars': [
        'warn',
        {
          vars: 'all',
          // Unused function arguments often indicate a mistake in JavaScript code.  However in TypeScript code,
          // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures
          // that are overriding a base class method or implementing an interface.
          args: 'none',
          // Unused error arguments are common and useful for inspection when a debugger is attached.
          caughtErrors: 'none'
        }
      ],

      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/no-use-before-define': [
        'error',
        {
          // Base ESLint options

          // We set functions=false so that functions can be ordered based on exported/local visibility
          // similar to class methods.  Also the base lint rule incorrectly flags a legitimate case like:
          //
          //   function a(n: number): void {
          //     if (n > 0) {
          //       b(n-1); //   lint error
          //     }
          //   }
          //   function b(n: number): void {
          //     if (n > 0) {
          //       a(n-1);
          //     }
          //   }
          functions: false,
          classes: true,
          variables: true,

          // TypeScript extensions

          enums: true,
          typedefs: true
          // ignoreTypeReferences: true
        }
      ],

      // TODO: This is a good rule for web browser apps, but it is commonly needed API for Node.js tools.
      // '@typescript-eslint/no-var-requires': 'error',

      // RATIONALE:         The "module" keyword is deprecated except when describing legacy libraries.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/prefer-namespace-keyword': 'warn',

      // RATIONALE:         We require explicit type annotations, even when the compiler could infer the type.
      //                    This can be a controversial policy because it makes code more verbose.  There are
      //                    a couple downsides to type inference, however.  First, it is not always available.
      //                    For example, when reviewing a pull request or examining a Git history, we may see
      //                    code like this:
      //
      //                        // What is the type of "y" here? The compiler knows, but the
      //                        // person reading the code may have no clue.
      //                        const x = f.();
      //                        const y = x.z;
      //
      //                    Second, relying on implicit types also discourages design discussions and documentation.
      //                    Consider this example:
      //
      //                        // Where's the documentation for "correlation" and "inventory"?
      //                        // Where would you even write the TSDoc comments?
      //                        function g() {
      //                          return { correlation: 123, inventory: 'xyz' };
      //                        }
      //
      //                    Implicit types make sense for small scale scenarios, where everyone is familiar with
      //                    the project, and code should be "easy to write".  Explicit types are preferable
      //                    for large scale scenarios, where people regularly work with source files they've never
      //                    seen before, and code should be "easy to read."
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      '@typescript-eslint/typedef': [
        'warn',
        {
          arrayDestructuring: false,
          arrowParameter: false,
          memberVariableDeclaration: true,
          objectDestructuring: false,
          parameter: true,
          propertyDeclaration: true,

          // This case is handled by our "@rushstack/typedef-var" rule
          variableDeclaration: false,

          // Normally we require type declarations for class members.  However, that rule is relaxed
          // for situations where we need to bind the "this" pointer for a callback.  For example, consider
          // this event handler for a React component:
          //
          //     class MyComponent {
          //       public render(): React.ReactNode {
          //          return (
          //            <a href="#" onClick={this._onClick}> click me </a>
          //          );
          //        }
          //
          //        // The assignment here avoids the need for "this._onClick.bind(this)"
          //        private _onClick = (event: React.MouseEvent<HTMLAnchorElement>): void => {
          //          console.log("Clicked! " + this.props.title);
          //        };
          //      }
          //
          // This coding style has limitations and should be used sparingly.  For example, "_onClick"
          // will not participate correctly in "virtual"/"override" inheritance.
          //
          // NOTE: This option affects both "memberVariableDeclaration" and "variableDeclaration" options.
          variableDeclarationIgnoreFunction: true
        }
      ],

      // ====================================================================
      // RECOMMENDED RULES
      // ====================================================================

      // RATIONALE:         This rule warns if setters are defined without getters, which is probably a mistake.
      'accessor-pairs': 'error',

      // RATIONALE:         In TypeScript, if you write x["y"] instead of x.y, it disables type checking.
      'dot-notation': [
        'warn',
        {
          allowPattern: '^_'
        }
      ],

      // RATIONALE:         Catches code that is likely to be incorrect
      eqeqeq: 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'for-direction': 'warn',

      // RATIONALE:         Catches a common coding mistake.
      'guard-for-in': 'error',

      // RATIONALE:         If you have more than 2,000 lines in a single source file, it's probably time
      //                    to split up your code.
      'max-lines': ['warn', { max: 2000 }],

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-async-promise-executor': 'error',

      // RATIONALE:         "|" and "&" are relatively rare, and are more likely to appear as a mistake when
      //                    someone meant "||" or "&&".  (But nobody types the other operators by mistake.)
      'no-bitwise': [
        'warn',
        {
          allow: [
            '^',
            // "|",
            // "&",
            '<<',
            '>>',
            '>>>',
            '^=',
            // "|=",
            //"&=",
            '<<=',
            '>>=',
            '>>>=',
            '~'
          ]
        }
      ],

      // RATIONALE:         Deprecated language feature.
      'no-caller': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-compare-neg-zero': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-cond-assign': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-constant-condition': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-control-regex': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-debugger': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-delete-var': 'error',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-duplicate-case': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-empty': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-empty-character-class': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-empty-pattern': 'warn',

      // RATIONALE:         Eval is a security concern and a performance concern.
      'no-eval': 'warn',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-ex-assign': 'error',

      // RATIONALE:         System types are global and should not be tampered with in a scalable code base.
      //                    If two different libraries (or two versions of the same library) both try to modify
      //                    a type, only one of them can win.  Polyfills are acceptable because they implement
      //                    a standardized interoperable contract, but polyfills are generally coded in plain
      //                    JavaScript.
      'no-extend-native': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-extra-boolean-cast': 'warn',

      'no-extra-label': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-fallthrough': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-func-assign': 'warn',

      // RATIONALE:         Catches a common coding mistake.
      'no-implied-eval': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-invalid-regexp': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-label-var': 'error',

      // RATIONALE:         Eliminates redundant code.
      'no-lone-blocks': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-misleading-character-class': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-multi-str': 'error',

      // RATIONALE:         It's generally a bad practice to call "new Thing()" without assigning the result to
      //                    a variable.  Either it's part of an awkward expression like "(new Thing()).doSomething()",
      //                    or else implies that the constructor is doing nontrivial computations, which is often
      //                    a poor class design.
      'no-new': 'warn',

      // RATIONALE:         Obsolete language feature that is deprecated.
      'no-new-func': 'error',

      // RATIONALE:         Obsolete language feature that is deprecated.
      'no-new-object': 'error',

      // RATIONALE:         Obsolete notation.
      'no-new-wrappers': 'warn',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-octal': 'error',

      // RATIONALE:         Catches code that is likely to be incorrect
      'no-octal-escape': 'error',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-regex-spaces': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-return-assign': 'error',

      // RATIONALE:         Security risk.
      'no-script-url': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-self-assign': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-self-compare': 'error',

      // RATIONALE:         This avoids statements such as "while (a = next(), a && a.length);" that use
      //                    commas to create compound expressions.  In general code is more readable if each
      //                    step is split onto a separate line.  This also makes it easier to set breakpoints
      //                    in the debugger.
      'no-sequences': 'error',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-shadow-restricted-names': 'error',

      // RATIONALE:         Obsolete language feature that is deprecated.
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-sparse-arrays': 'error',

      // RATIONALE:         Although in theory JavaScript allows any possible data type to be thrown as an exception,
      //                    such flexibility adds pointless complexity, by requiring every catch block to test
      //                    the type of the object that it receives.  Whereas if catch blocks can always assume
      //                    that their object implements the "Error" contract, then the code is simpler, and
      //                    we generally get useful additional information like a call stack.
      'no-throw-literal': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-unmodified-loop-condition': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-unsafe-finally': 'error',

      // RATIONALE:         Catches a common coding mistake.
      'no-unused-expressions': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-unused-labels': 'warn',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-useless-catch': 'warn',

      // RATIONALE:         Avoids a potential performance problem.
      'no-useless-concat': 'warn',

      // RATIONALE:         The "var" keyword is deprecated because of its confusing "hoisting" behavior.
      //                    Always use "let" or "const" instead.
      //
      // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
      'no-var': 'error',

      // RATIONALE:         Generally not needed in modern code.
      'no-void': 'error',

      // RATIONALE:         Obsolete language feature that is deprecated.
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'no-with': 'error',

      // RATIONALE:         Makes logic easier to understand, since constants always have a known value
      // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js
      'prefer-const': 'warn',

      // RATIONALE:         Catches a common coding mistake where "resolve" and "reject" are confused.
      'promise/param-names': 'error',

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'require-atomic-updates': 'error',

      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'require-yield': 'warn',

      // "Use strict" is redundant when using the TypeScript compiler.
      strict: ['error', 'never'],

      // RATIONALE:         Catches code that is likely to be incorrect
      // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
      'use-isnan': 'error'

      // The "no-restricted-syntax" rule is a general purpose pattern matcher that we can use to experiment with
      // new rules.  If a rule works well, we should convert it to a proper rule so it gets its own name
      // for suppressions and documentation.
      // How it works:    https://eslint.org/docs/rules/no-restricted-syntax
      // AST visualizer:  https://astexplorer.net/
      // Debugger:        http://estools.github.io/esquery/
      //
      // "no-restricted-syntax": [
      // ],
    }
  },

  // ====================================================================
  // TESTING RULES
  // ====================================================================
  {
    files: [
      // Test files
      '**/*.test.ts',
      '**/*.test.tsx',
      '**/*.spec.ts',
      '**/*.spec.tsx',

      // Facebook convention
      '**/__mocks__/*.ts',
      '**/__mocks__/*.tsx',
      '**/__tests__/*.ts',
      '**/__tests__/*.tsx',

      // Microsoft convention
      '**/test/*.ts',
      '**/test/*.tsx'
    ],
    rules: {
      // Unit tests sometimes use a standalone statement like "new Thing(123);" to test a constructor.
      'no-new': 'off',

      // Jest's mocking API is designed in a way that produces compositional data types that often have
      // no concise description.  Since test code does not ship, and typically does not introduce new
      // concepts or algorithms, the usual arguments for prioritizing readability over writability can be
      // relaxed in this case.
      '@rushstack/typedef-var': 'off'
    }
  }
];

module.exports = { commonNamingConventionSelectors, commonConfig };
